/**
 * Parser.java
 *
 * @author      Lobsang Alvites Szaler
 * @version     1.0
 * @date        Aug 7 2007
 *
 * Copyright (c) Soluciones Info Móviles SAC
 *
 */

package sim.utils.parser;

import java.util.*;
import sim.utils.*;

public class Parser {

    // Posibles flags
    public static final int PATTERN = 0x1;
    public static final int FORWARD = 0x2;
    public static final int BACKWARD = 0x0; // Por compatibilidad ;-)

    // Clase que implementa scan.
    private ParserScan m_Scanner;

    // Flags
    private int m_Flags;

    // Token
    private byte[] m_Token;
    private int m_TokStar;
    private int m_TokLength;

    /**
     * Constructores
     * 
     */
    public Parser(final ParserScan scanner) {
	m_Scanner = scanner;
    }

    public Parser(final ParserScan scanner, final byte[] token, int start, int length, int flags) {
	m_Scanner = scanner;
	init(token, start, length, flags);
    }

    public Parser(final ParserScan scanner, final byte[] token, int length, int flags) {
	this(scanner, token, 0, length, flags);
    }

    public Parser(final ParserScan scanner, final byte[] token, int flags) {
	this(scanner, token, 0, token.length, flags);
    }

    public Parser(final byte[] token, int start, int length, int flags) {
	init(token, start, length, flags);
    }

    /** 
     * Establece la clase que implementa la función de barrido.
     * 
     * @param scanner 
     */
    public void setParserScan(final ParserScan scanner) {
	m_Scanner = scanner;
    }

    /** 
     * Cambia las condiciones de anáisis.
     * 
     * @param token Arreglo de bytes que contiene el token.
     * @param start Posición del primer byte del token.
     * @param length longitud del token.
     * @param flags 
     */
    public void init(final byte[] token, int start, int length, int flags) {
	m_Token = token;
	m_TokStar = start;
	m_TokLength = length;
	m_Flags = flags;
    }

    public boolean scan(final byte[] line, int start, int length, Object something) {
	if ((m_Flags & Parser.PATTERN) > 0) {
	    return scanPattern(line, start, length, something);
	}
	else {
	    return scanString(line, start, length, something);
	}
    }
   
    public boolean scan(final byte[] line, int start, int length) {
	return scan (line, start, length, null);
    }

    public boolean scan(final byte[] line, int start, Object something) {
	return scan(line, start, line.length - start, something);
    }

    public boolean scan(final byte[] line, int start) {
	return scan(line, start, line.length - start, null);
    }

    public boolean scan(final byte[] line, Object something) {
	return scan(line, 0, line.length, something);
    }

    public boolean scan(final byte[] line) {
	return scan(line, null);
    }

    public boolean scan(final String line, Object something) {
	byte[] bytes;
	bytes = line.getBytes();
	return scan(bytes, 0, bytes.length, something);
    }

    public boolean scan(final String line) {
	return scan(line, null);
    }

    private boolean scanString(final byte[] line, int start, int length, Object something) {
        int hits;
	int eureka;
	int finish;
	boolean cancel;

        hits = 0;
	cancel = false;

        if((m_Flags & Parser.FORWARD) > 0) {
            do {
		// Buscar el token
                eureka = Utils.memmem(line, start, length, 
				      m_Token, m_TokStar, m_TokLength);
                if(eureka == -1) {
		    // El token imaginario :-)
                    eureka = start + length;
                }
		// Notificar que se encontro una coincidencia.
                if(!m_Scanner.parserHit(++hits, line, start, eureka - start, something)) {
                    cancel = true;
		    break;
                }
		length = length - ((eureka + m_TokLength) - start);
                start = (eureka + m_TokLength);
            } while (length > 0);            
        } 
	else { // BACKWARD
	    finish = start + length;
	    do {
		// Buscar el token
                eureka = Utils.memmem(line, finish, length - (finish - start),
				      m_Token, m_TokStar, m_TokLength);
		if (eureka == finish) {
		    // Notificar que se encontro una coincidencia.
		    if(!m_Scanner.parserHit(++hits, 
				       line, 
				       finish + m_TokLength, 
				       length - (finish - start) - m_TokLength,
				       something)) {
			cancel = true;
			break;
		    }
		    length -= (finish - start);
		}
		finish -= m_TokLength;
            } while (finish > start);
	    // Notificar la última coincidencia :-)
	    if (finish <= start) {
		m_Scanner.parserHit(hits++, line, start, length, something);
	    }
        }

	// Notificar que el parse ha terminado.
        return m_Scanner.parserEnd(cancel);
    }

    private boolean scanPattern(final byte[] line, int start, int length, Object something) {
	int hits;
	int hitlen;
	boolean cancel;

	hits = 0;

	cancel = false;

	if ((m_Flags & Parser.FORWARD) > 0) {
	    while(length > 0) {
		hitlen = 0;
		// Calcular la longitud del hit
		while((hitlen < length) &&
		      (Utils.memmem(m_Token, m_TokStar, m_TokLength,  
				    line, start + hitlen, 1) == -1)) {
		    hitlen++;
		}
		// Notificar si se encontro un patrón
		if(!m_Scanner.parserHit(++hits, line, start, hitlen, something)) {
		    cancel = true;
		    break;
		}
		// Ignorar la longitud del token
		start += hitlen;
		length -= hitlen;
		while((length > 0) && 
		      (Utils.memmem(m_Token, m_TokStar, m_TokLength,  
				    line, start , 1) > -1)) {
		    start++;
		    length--;
		}
	    }
	}
	else {
	    while(length > 0) {
		hitlen = 0;
		// Calcular la longitud del hit
		while((hitlen < length) &&
		      (Utils.memmem(m_Token, m_TokStar, m_TokLength, 
				    line, start + (length - 1) - hitlen, 1) == -1)) {
		    hitlen--;
		}
		// Notificar si se encontro un patrón
		if(!m_Scanner.parserHit(++hits, 
					line, 
					start + (length - 1) - hitlen, 
					hitlen, 
					something)) {
		    cancel = true;
		    break;
		}
		length -= hitlen;
		// Ignorar la longitud del token
		while((length > 0) &&
		      (Utils.memmem(m_Token, m_TokStar, m_TokLength, 
				    line, start + length - 1) > -1)) {
		    length--;
		}
	    }
	}

	return m_Scanner.parserEnd(cancel);
    }
}
